package com.ease.util.keyboard {
   import flash.events.KeyboardEvent;
   import flash.utils.Dictionary;

   import mx.collections.ArrayCollection;
   import mx.core.FlexGlobals;

   /**
    * Class to track top-level shortcut keys
    */
   public class KeyboardShortcutManager {
      public static const CTRL_KEY:int  = 1;
      public static const SHIFT_KEY:int = 2;
      public static const ALT_KEY:int   = 4;

      private static var _instance:KeyboardShortcutManager = null;

      private var _shortcutInfos:Dictionary;

      /**
       * This is a singleton class, and should not be instansiated directly.
       * Use the <code>instance</code> getter instead.
       */
      public function KeyboardShortcutManager() {
         if (_instance != null) {
            throw new Error("KeyboardShortcutManager cannot be instantiated more than once.");
         }

         initialize();
      }

      /**
       * Returns the singleton instance.
       */
      public static function get instance():KeyboardShortcutManager {
         if (_instance == null) {
            _instance = new KeyboardShortcutManager();
         }
         return _instance;
      }

      /**
       * The <code>KeyboardEvent.KEY_DOWN</code> event handler. This handler
       * should be registered in the <code>applicationComplete</code>
       * handler for any application which wishes to use the
       * KeyboardShortcutManager
       */
      public function onKeyDown(event:KeyboardEvent):void {
         // if no modifiers were specified, it's not a shortcut so return
         if (!(event.ctrlKey) && !(event.shiftKey) && !(event.altKey)) {
            return;
         }

         if (FlexGlobals.topLevelApplication.systemManager.numModalWindows > 0) {
            return;
         }

         var shortcutInfos:ArrayCollection = retrieveShortcutInfos(event.keyCode);

         if (shortcutInfos != null) {
            for each (var shortcutInfo:KeyboardShortcutInfo in shortcutInfos) {
               if (shortcutInfo.isRequestedShortcut(event.keyCode, event.ctrlKey,
                  event.shiftKey, event.altKey)) {
                  shortcutInfo.handler(event.keyCode, shortcutInfo.shortcutID);
               }
            }
         }
      }

      /**
       * Registers a shortcut to an unclaimed key.
       *
       * @param keyCode
       *        The uint keycode (as in flash.ui.Keyboard values) for the key to be bound
       *        to a Function
       *
       * @param keyHandler
       *        The function to be invoked when the specified key is hit. The signature is:
       *        <code>function keyHandler(keyCode:uint):void</code>
       *
       * @param modifiers
       *        mask indicating which modifier keys should be applied to the shortcut
       *
       * @return
       *        a unique uint identifier for the shortcut being registered
       */
      public function registerKey(keyCode:int, keyHandler:Function, modifiers:uint):uint {
         var shortcutInfos:ArrayCollection = retrieveShortcutInfos(keyCode);

         if (shortcutInfos == null) {
            shortcutInfos = new ArrayCollection();

            _shortcutInfos[keyCode] = shortcutInfos;
         }

         var shortcutInfo:KeyboardShortcutInfo =
            new KeyboardShortcutInfo(keyCode, keyHandler, modifiers);

         shortcutInfos.addItem(shortcutInfo);

         return shortcutInfo.shortcutID;
      }

      /**
       * Removes a shortcut to a key.
       *
       * @param keyCode
       *        The uint keycode (as in flash.ui.Keyboard values) for the key to be unbound
       *
       * @param shortcutID
       *        the ID that is associated with the shortcut; returned from
       *        <code>registerKey</code>
       *
       * @return
       *        false if there is not a handler for the specified key code
       *        true otherwise
       */
      public function unregisterKey(keyCode:uint, shortcutID:uint):Boolean {
         var info:KeyboardShortcutInfo;
         var shortcutInfos:ArrayCollection =
            retrieveShortcutInfos(keyCode);

         if (shortcutInfos != null) {
            for (var index:int = 0; index < shortcutInfos.length; index++) {
               info = shortcutInfos.getItemAt(index) as KeyboardShortcutInfo;

               if (shortcutID == info.shortcutID) {
                  shortcutInfos.removeItemAt(index);
                  break;
               }
            }
         }

         return true;
      }

      /**
       * Initializer for the keyboard shortcut manager
       */
      private function initialize():void {
         // allocate an array to hold shortcut information
         _shortcutInfos = new Dictionary();

         // add an event listener to catch keyboard key down events
         FlexGlobals.topLevelApplication.stage.addEventListener(KeyboardEvent.KEY_DOWN,
            onKeyDown);
      }

      private function retrieveShortcutInfos(keyCode:uint):ArrayCollection {
         if (!_shortcutInfos.hasOwnProperty(keyCode)) {
            return null;
         }

         return _shortcutInfos[keyCode] as ArrayCollection;

      }

      /**
       *  A method to retrieve keyboard shortcut info based on a specified key and
       *  modifier keys.
       *
       * @param keyCode
       *        The uint keycode (as in flash.ui.Keyboard values) for the key to be unbound
       *
       * @param ctrlKey
       *        boolean indicating whether or not the Ctrl key should be pressed to
       *        invoke the associated handler
       *
       * @param shiftKey
       *        boolean indicating whether or not the Shift key should be pressed to
       *        invoke the associated handler
       *
       * @param altKey
       *        boolean indicating whether or not the Alt key should be pressed to
       *        invoke the associated handler
       *
       * @return
       *        a KeyboardShortcutInfo object whose keycode and modifier key flags
       *        the arguments passed in to the method, if any exist
       *        null otherwuse
       */
      private function retrieveShortcutInfo(keyCode:uint, ctrlKey:Boolean = false,
                                            shiftKey:Boolean = false, altKey:Boolean = false):KeyboardShortcutInfo {
         var keyHandlers:ArrayCollection = retrieveShortcutInfos(keyCode);
         var keyModifiers:int = makeModifierMask(ctrlKey, shiftKey, altKey);

         if (null != keyHandlers) {
            for each (var shortcutInfo:KeyboardShortcutInfo in keyHandlers) {
               if (shortcutInfo.modifiers == keyModifiers) {
                  return shortcutInfo;
               }
            }
         }

         return null;
      }

      /**
       * A utility method to create a mask indicating a combination of one
       * or more modifier keys (Ctrl, Shift, Alt)
       */
      public static function makeModifierMask(ctrlKey:Boolean, shiftKey:Boolean,
                                              altKey:Boolean):int {
         var keyModifiers:int = 0;

         if (ctrlKey) {
            keyModifiers |= CTRL_KEY;
         }

         if (shiftKey) {
            keyModifiers |= SHIFT_KEY;
         }

         if (altKey) {
            keyModifiers |= ALT_KEY;
         }

         return keyModifiers;
      }
   }
}